home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 7: Sunsite
/
Linux Cubed Series 7 - Sunsite Vol 1.iso
/
system
/
news
/
readers
/
skim-0.8
/
skim-0
/
skim-0.8.4
/
AutoSelectAndKill.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-02-18
|
16KB
|
656 lines
/*
* NAME
* AutoSelectAndKill.c
* USAGE
* AutoSelectAndKill NewsGroup
* DESCRIPTION
* Functions to automatically select and kill subjects and authors.
* COPYRIGHT
* Skim - Off-line news reading package optimized for slow lines.
* Copyright (C) 1996 Rene W.J. Pijlman
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* VERSION
* Skim version 0.8.4.
*/
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <regex.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "AutoSelectAndKill.h"
#include "Skim.h"
#include "SkimUtils.h"
#include "MemAlloc.h"
#include "VarBuf.h"
#include "ArticleNumber.h"
#include "StandardIO.h"
FILE_ID("$Header: /home/rene/sys/CVS_MasterSourceRepository/skim/AutoSelectAndKill.c,v 1.7 1996/02/17 22:47:29 rene Exp $");
#define USAGE "Usage: AutoSelectAndKill NewsGroup"
#define ARGC_NEWSGROUP 1
#define NUMBER_OF_ARGUMENTS 2
#define REGEX_MAPSIZE 256
static void DestroyPattern( regex_t * Pattern )
{
if ( Pattern != NULL )
{
regfree( Pattern );
MemFree( Pattern );
}
}
/* Like popen(3), but this implementation doesn't start a shell process. */
static void OpenPipeToM4( StandardIO Pipe, const char * IncludeFileName )
{
int fd[2];
int pid;
VarBuf PseudoFileName = VBCreate();
VBPrintf( PseudoFileName, "m4 %s", IncludeFileName );
if ( pipe(fd) < 0 )
{
perror("pipe");
exit( EXIT_FAILURE );
}
pid = fork();
if ( pid > 0 )
{
/* Parent. */
close(fd[1]);
SIOFileOpenFileDescriptorVB(Pipe, fd[0], PseudoFileName, OpenModeRead);
}
else if ( pid == 0 )
{
/* Child. */
close(fd[0]);
if ( dup2( fd[1], 1 ) < 0 )
{
perror("dup");
exit( EXIT_FAILURE );
}
close(fd[1]);
if ( execlp( "m4", "m4", IncludeFileName, NULL ) < 0 )
{
SIOPrintError( VBAsString( PseudoFileName ) );
exit( EXIT_FAILURE );
}
}
else
{
perror( "fork" );
exit( EXIT_FAILURE );
}
VBDestroy(PseudoFileName);
}
/*
* SIDE EFFECTS
* CompilePatternInFile changes the working directory to
* $SKIMDIR/PatternDirectory.
*/
static regex_t * CompilePatternInFile(
char * PatternDirectory,
char * FileName,
Boolean CaseSensitive )
{
StandardIO PatternFile = SIOCreate();
regex_t * Pattern = NULL;
VarBuf M4Command = VBCreate();
VarBuf RegularExpression = VBCreate();
VarBuf SubExpression = VBCreate();
VarBuf CurrentWorkingDirectory = VBCreate();
int ErrorCode;
Boolean FirstSubExpression = True;
VBPrintf( CurrentWorkingDirectory, "%s/%s", SkimDirectory(),
PatternDirectory );
if ( chdir( VBAsString( CurrentWorkingDirectory ) ) != 0 )
{
perror( "chdir" );
exit( EXIT_FAILURE );
}
OpenPipeToM4( PatternFile, FileName );
while ( VBReadLine( SubExpression, PatternFile, WITHOUT_NEWLINE ) )
{
/* Ignore empty lines and comment. */
if ( VBSize( SubExpression ) > 0 &&
*VBAsString( SubExpression ) != '#' )
{
if ( !FirstSubExpression )
{
VBAppendCharacter( RegularExpression, '|' );
}
VBAppendVB( RegularExpression, SubExpression );
FirstSubExpression = False;
}
VBReset( SubExpression );
}
SIOFileClose( PatternFile );
WaitForAChild( VBAsString( M4Command ) );
if ( VBSize(RegularExpression) > 0 )
{
int CompilationFlags = REG_EXTENDED | REG_NOSUB |
(CaseSensitive ? 0 : REG_ICASE);
Pattern = MemAlloc( sizeof(regex_t) );
ErrorCode = regcomp( Pattern, VBAsString(RegularExpression),
CompilationFlags );
if ( ErrorCode != 0 )
{
#define ERROR_BUFFER_SIZE 1024
char ErrorMessage[ERROR_BUFFER_SIZE];
(void)regerror(ErrorCode, Pattern, ErrorMessage, ERROR_BUFFER_SIZE);
SIOPrintf( StandardError,
"Error compiling the %s pattern of group %s:\n%s\n",
PatternDirectory, FileName, ErrorMessage );
exit( EXIT_FAILURE );
}
}
VBDestroy( CurrentWorkingDirectory );
VBDestroy( M4Command );
VBDestroy( SubExpression );
VBDestroy( RegularExpression );
SIODestroy(PatternFile);
return Pattern;
}
static void GetSubjectFromHeaderLine(
VarBuf HeaderLine,
VarBuf Subject )
{
if ( !AppendFieldFromFixedRecord( Subject, HeaderLine,
OffsetOfSubject(), VARIABLE_LENGTH,
VBRemoveTrailingBlanks ) )
{
SIOPrintf( StandardError,
"No subject field in header line.\n" );
exit( EXIT_FAILURE );
}
}
static void GetAuthorFromHeaderLine(
VarBuf HeaderLine,
VarBuf Author )
{
if ( !AppendFieldFromFixedRecord( Author, HeaderLine, OffsetOfAuthor(),
LengthOfAuthor(),
VBRemoveTrailingBlanks ) )
{
SIOPrintf( StandardError, "No author field in header line.\n" );
exit( EXIT_FAILURE );
}
}
static void GetArticleNumberFromHeaderLine(
VarBuf HeaderLine,
VarBuf ArticleNumber )
{
if ( !AppendFieldFromFixedRecord( ArticleNumber, HeaderLine,
OffsetOfArticleNumber(),
LengthArticleNumber,
VBRemoveTrailingBlanks ) )
{
SIOPrintf( StandardError,
"No article number in header line.\n" );
exit( EXIT_FAILURE );
}
}
static StandardIO KilledFile = NULL;
static StandardIO SubjectsFile = NULL;
VarBuf FullPathKilledFile = NULL;
VarBuf FullPathSubjectsFile = NULL;
static VarBuf ArrayOfHeaderLines = NULL;
static void HeaderLineToSubjectsFile(
char * Newsgroup,
VarBuf HeaderLine,
Boolean AutoSelected )
{
VarBuf NewHeaderLine = VBCreate();
assert( ArrayOfHeaderLines != NULL );
assert( VBSize(HeaderLine) > 0 && *VBAsString(HeaderLine) == NotSelected );
VBPrintf( NewHeaderLine, "%c%s", AutoSelected ? Selected : NotSelected,
VBAsString(HeaderLine) + 1 );
VBAppendVBReference( ArrayOfHeaderLines, NewHeaderLine );
/* NewHeaderLine is destroyed in ASAKClose(). */
}
static void AutoSelectHeaderLine(
char * Newsgroup,
VarBuf HeaderLine )
{
HeaderLineToSubjectsFile( Newsgroup, HeaderLine, True );
}
static void NormalHeaderLine(
char * Newsgroup,
VarBuf HeaderLine )
{
HeaderLineToSubjectsFile( Newsgroup, HeaderLine, False );
}
static void KillHeaderLine(
char * Newsgroup,
VarBuf HeaderLine )
{
assert( Newsgroup != NULL && strlen(Newsgroup) > 0 );
if ( KilledFile == NULL )
{
assert( FullPathKilledFile == NULL );
KilledFile = SIOCreate();
FullPathKilledFile = VBCreate();
VBPrintf( FullPathKilledFile, "%s/%s/%s",
SkimDirectory(), "Killed", Newsgroup );
SIOFileOpenVB( KilledFile, FullPathKilledFile, OpenModeAppend );
}
assert( FullPathKilledFile != NULL );
SIOPrintf( KilledFile, "%s\n", VBAsString(HeaderLine) );
}
static Boolean Open = False;
static VarBuf CurrentNewsgroup = NULL;
static ArticleNumber HighestArticleNumber = 0;
static Boolean SubjectCaseSensitive;
static Boolean AuthorCaseSensitive;
static regex_t * AutoSelectSubject = NULL;
static regex_t * KillSubject = NULL;
static regex_t * AutoSelectAuthor = NULL;
static regex_t * KillAuthor = NULL;
void ASAKOpen( const char * Newsgroup )
{
assert( !Open );
assert( Newsgroup != NULL && strlen(Newsgroup) > 0 );
assert( CurrentNewsgroup == NULL );
CurrentNewsgroup = VBCreate();
VBAppendString( CurrentNewsgroup, Newsgroup );
assert (ArrayOfHeaderLines == NULL );
ArrayOfHeaderLines = VBCreate();
HighestArticleNumber = 0;
Open = True;
}
#define RE_PREFIX "Re: "
#define LEN_RE_PREFIX 4
/*
* Compare two subjects (case insensitive), ignoring the optional "Re: "
* prefix.
*/
static int PseudoThread( VarBuf Buf1, VarBuf Buf2 )
{
char * p;
char * q;
int Compare;
Boolean pIsReply;
if ( VBSize(Buf1) < OffsetOfSubject() )
{
SIOPrintf( StandardError, "Incorrect format: %V\n", Buf1 );
exit(EXIT_FAILURE);
}
if ( VBSize(Buf2) < OffsetOfSubject() )
{
SIOPrintf( StandardError, "Incorrect format: %V\n", Buf2 );
exit(EXIT_FAILURE);
}
p = VBAsString( Buf1 ) + OffsetOfSubject();
q = VBAsString( Buf2 ) + OffsetOfSubject();
if ( !strncasecmp( p, RE_PREFIX, LEN_RE_PREFIX ) )
{
pIsReply = True;
p += LEN_RE_PREFIX;
}
else
{
pIsReply = False;
}
if ( !strncasecmp( q, RE_PREFIX, LEN_RE_PREFIX ) )
{
q += LEN_RE_PREFIX;
}
Compare = strcasecmp( p, q );
/* A reply sorts after the subject it replies to. */
if ( Compare == 0 && pIsReply )
{
Compare = 1;
}
return Compare;
}
/* Read the subjects from the Subjects file into memory for sorting. */
static void ReadOldSubjects(
VarBuf ArrayOfHeaderLines,
VarBuf CurrentNewsgroup )
{
StandardIO SubjectsFile = SIOCreate();
VarBuf FullPathSubjectsFile = VBCreate();
assert( VBIsOK(ArrayOfHeaderLines) );
assert( VBIsOK(CurrentNewsgroup) && VBSize(CurrentNewsgroup) > 0 );
VBPrintf( FullPathSubjectsFile, "%s/%s/%V",
SkimDirectory(), "Subjects", CurrentNewsgroup );
SIOFileOpenVB( SubjectsFile, FullPathSubjectsFile, OpenModeReadIgnore );
if ( SIOIsOpenForRead( SubjectsFile ) )
{
VarBuf HeaderLine = VBCreate();
while ( VBReadLine( HeaderLine, SubjectsFile, WITHOUT_NEWLINE ) )
{
VBAppendVBReference( ArrayOfHeaderLines, HeaderLine );
/*
* The HeaderLine which we just appended to the array will be
* destroyed in ASAKClose().
*/
HeaderLine = VBCreate();
}
VBDestroy( HeaderLine );
}
VBDestroy( FullPathSubjectsFile );
SIODestroy( SubjectsFile );
}
void ASAKClose( void )
{
int i;
VarBuf * p;
assert( Open );
if ( KilledFile != NULL )
{
SIODestroy( KilledFile );
KilledFile = NULL;
VBDestroy( FullPathKilledFile );
FullPathKilledFile = NULL;
}
/*
* We're about to write the new subjects to the Subjects file. To preserve
* pseudothreading, we must read in the old subjects before sorting.
*/
ReadOldSubjects( ArrayOfHeaderLines, CurrentNewsgroup );
VBSortArrayOfVB( ArrayOfHeaderLines, PseudoThread );
p = VBAddress( ArrayOfHeaderLines );
for ( i = 0; i < VBSize(ArrayOfHeaderLines) / sizeof(VarBuf); i++ )
{
if ( SubjectsFile == NULL )
{
assert( FullPathSubjectsFile == NULL );
SubjectsFile = SIOCreate();
FullPathSubjectsFile = VBCreate();
VBPrintf( FullPathSubjectsFile, "%s/%s/%V",
SkimDirectory(), "Subjects", CurrentNewsgroup );
SIOFileOpenVB( SubjectsFile, FullPathSubjectsFile,
OpenModeWriteDiscardOld );
}
assert( FullPathSubjectsFile != NULL );
SIOPrintf( SubjectsFile, "%V\n", p[i] );
VBDestroy( p[i] );
}
VBDestroy( ArrayOfHeaderLines );
SIODestroy( SubjectsFile );
VBDestroy( FullPathSubjectsFile );
FullPathSubjectsFile = NULL;
if ( HighestArticleNumber > 0 )
{
SetCurrentArticleNumber( VBAsString(CurrentNewsgroup),
HighestArticleNumber + 1 );
}
DestroyPattern( AutoSelectSubject );
AutoSelectSubject = NULL;
DestroyPattern( KillSubject );
KillSubject = NULL;
DestroyPattern( AutoSelectAuthor );
AutoSelectAuthor = NULL;
DestroyPattern( KillAuthor );
KillAuthor = NULL;
VBDestroy( CurrentNewsgroup );
Open = False;
}
void ASAKExecute( VarBuf HeaderLine )
{
VarBuf Subject = VBCreate();
VarBuf Author = VBCreate();
VarBuf ArticleNumberInVB = VBCreate();
static Boolean Initialized = False;
ArticleNumber CurrentArticleNumber;
char * SkimAutoSubject = NULL;
char * SkimAutoAuthor = NULL;
assert( Open );
if ( !Initialized )
{
/*
* Automatic selection and killing of subjects must be enabled by the
* user.
*/
if ( ( SkimAutoSubject = getenv("SKIMAUTOSUBJECT") ) != NULL )
{
if ( !strcmp( SkimAutoSubject, "CaseSensitive" ) )
{
SubjectCaseSensitive = True;
}
else if ( !strcmp( SkimAutoSubject, "CaseInsensitive" ) )
{
SubjectCaseSensitive = False;
}
else
{
SIOPrintf( StandardError,
"Invalid value in $SKIMAUTOSUBJECT: %s\n",
SkimAutoSubject );
exit( EXIT_FAILURE );
}
assert( AutoSelectSubject == NULL );
AutoSelectSubject = CompilePatternInFile(
"Patterns/Subject/AutoSelect",
VBAsString( CurrentNewsgroup ),
SubjectCaseSensitive );
assert( KillSubject == NULL );
KillSubject = CompilePatternInFile(
"Patterns/Subject/Kill",
VBAsString( CurrentNewsgroup ),
SubjectCaseSensitive );
}
/*
* Automatic selection and killing of authors must be enabled by the
* user.
*/
if ( ( SkimAutoAuthor = getenv("SKIMAUTOAUTHOR") ) != NULL )
{
if ( !strcmp( SkimAutoAuthor, "CaseSensitive" ) )
{
AuthorCaseSensitive = True;
}
else if ( !strcmp( SkimAutoAuthor, "CaseInsensitive" ) )
{
AuthorCaseSensitive = False;
}
else
{
SIOPrintf( StandardError,
"Invalid value in $SKIMAUTOAUTHOR: %s\n",
SkimAutoAuthor );
exit( EXIT_FAILURE );
}
assert( AutoSelectAuthor == NULL );
AutoSelectAuthor = CompilePatternInFile(
"Patterns/Author/AutoSelect",
VBAsString( CurrentNewsgroup ),
AuthorCaseSensitive );
assert( KillAuthor == NULL );
KillAuthor = CompilePatternInFile(
"Patterns/Author/Kill",
VBAsString( CurrentNewsgroup ),
AuthorCaseSensitive );
}
Initialized = True;
}
if ( AutoSelectSubject != NULL || KillSubject != NULL )
{
GetSubjectFromHeaderLine( HeaderLine, Subject );
}
if ( AutoSelectAuthor != NULL || KillAuthor != NULL )
{
GetAuthorFromHeaderLine( HeaderLine, Author );
}
if ( AutoSelectSubject != NULL &&
!regexec( AutoSelectSubject, VBAsString(Subject), 0, NULL, 0 ) )
{
AutoSelectHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
}
else if ( AutoSelectAuthor != NULL &&
!regexec( AutoSelectAuthor, VBAsString(Author), 0, NULL, 0 ) )
{
AutoSelectHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
}
else if ( KillSubject != NULL &&
!regexec( KillSubject, VBAsString(Subject), 0, NULL, 0 ) )
{
KillHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
}
else if ( KillAuthor != NULL &&
!regexec( KillAuthor, VBAsString(Author), 0, NULL, 0 ) )
{
KillHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
}
else
{
NormalHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
}
GetArticleNumberFromHeaderLine( HeaderLine, ArticleNumberInVB );
if ( sscanf( VBAsString(ArticleNumberInVB), "%lu",
&CurrentArticleNumber ) != EOF )
{
if ( CurrentArticleNumber > HighestArticleNumber )
{
HighestArticleNumber = CurrentArticleNumber;
}
}
else
{
SIOPrintf( StandardError, "Format error in header line: %V.\n",
HeaderLine );
exit( EXIT_FAILURE );
}
VBDestroy( Subject );
VBDestroy( Author );
VBDestroy( ArticleNumberInVB );
}